﻿using System;
using System.Collections.Generic;
using System.IO;
using Co9320.NamaAlert.Model;
using System.Linq;


namespace Co9320.NamaAlert.Engine.TagService
{
	/// <summary>
	/// TagServiceのクライアント側統合インターフェース
	/// </summary>
	public partial class TagService:IDisposable
	{
		#region literal
		private const int TagMaxLength = 20;
		private const int CasterTagMaxCount = 2;
		#endregion

		#region StaticMethod and Fields.
		private static TagService SingltonObject;
		private static System.Globalization.StringInfo info;
		private static bool IsInitialized=false;
		private static bool IsStarted=false;


		/// <summary>
		/// シングルトンオブジェクトのイニシャライザ｡
		/// </summary>
		/// <param name="localDatabaseFilePath">
		/// ローカルで利用する､sdfファイルの絶対パス｡
		/// </param>
		/// <param name="applicationId">
		/// C/S通信で利用するApplicationIdを指定します｡
		/// </param>
		/// <param name="keySeed">
		/// C/S通信で利用するTokenのKeySeedを指定します｡
		/// </param>
		/// <remarks>
		/// <para>複数回このメソッドを呼び出した場合､最初の1回を除き全て無視されます｡</para>
		/// <para>LocalDataBaseFilePathが無効なパスを示していたり､nullの場合は､デフォルトで初期化します｡</para>
		/// </remarks>
		public static void Initialize(string LocalDatabaseFilePath,string keySeed,string applicationId)
		{
			if (!IsInitialized)
			{
				string path=PathCheck(LocalDatabaseFilePath);
				SingltonObject = new TagService(path, new TimeSpan(0, 0, 5, 0, 0), new TimeSpan(0, 0, 30, 0, 0), keySeed, applicationId);

				IsInitialized = true;
			}
			else
			{
				return;
			}
		}

		private static string PathCheck(string localDatabaseFilePath)
		{
			if (localDatabaseFilePath != null)
			{
				FileInfo info = new FileInfo(localDatabaseFilePath);
				info.Refresh();
				if (info.Exists)
				{
					return localDatabaseFilePath;
				}
				else
				{
					info.Directory.Refresh();
					if (!info.Directory.Exists)
					{
						try
						{
							info.Directory.Create();
						}
						catch
						{
							throw new IOException("ディレクトリを作成できません");
						}
					}
					else
					{
						try
						{
							info.Create().Dispose();
						}
						catch
						{
							throw new IOException("ファイルを作成できません");

						}
					}

					LocalDatabaseController.CreateDatabase(localDatabaseFilePath,true);
					return localDatabaseFilePath;
				}
			}
			else
			{
				throw new IOException("FilePathjがNullです｡");
			}
		}

		/// <summary>
		/// TagServiceClassのインスタンスを取得します｡
		/// </summary>
		/// <returns>取得されたインスタンスが戻ります｡</returns>
		/// <remarks>
		/// <para>Initializeメソッドを呼ぶ前に､このメソッドを呼び出した場合､</para>
		/// </remarks>
		public static TagService GetTagService()
		{
			if (!IsInitialized)
			{
				throw new IOException();
			}
			
			System.Diagnostics.Trace.Assert(SingltonObject != null);

			if (!IsStarted)
			{
#if ENABLE_SERVER
				SingltonObject.ServerController.Start();
#endif
				IsStarted = true;
			}

			return SingltonObject;
		}

		#endregion



		/// <summary>
		/// MyTag制御用
		/// </summary>
		private LocalDatabaseController LocalController;

#if ENABLE_SERVER
		private CommunicationControl.ServerCommunicator ServerController;
#endif

		///// <summary>
		///// UserTag制御用
		///// </summary>
		//private SharedTagServiceObject SharedCommunicator;
		/// <summary>
		/// 内部コンストラクタ｡
		/// Initilizeからのみ呼ばれる｡
		/// </summary>
		private TagService(string localDatabaseFilePath, TimeSpan communicationInterval,TimeSpan errorInterval,string keySeed,string applicationId)
		{
			LocalController = new LocalDatabaseController(localDatabaseFilePath);
			info = new System.Globalization.StringInfo();

#if ENEBLE_SERVER_
			ServerController = new CommunicationControl.ServerCommunicator(applicationId, keySeed,
				communicationInterval,errorInterval);
#endif
		}


		/// <summary>
		/// UserTagの取得が完了した際に発生するイベントです｡
		/// </summary>
		public event EventHandler<TagsChangedEventArgs> TagChanged
		{
			add
			{
#if ENABLE_SERVER
				this.ServerController.TagChanged += value;
#endif
			}
			remove
			{
#if ENABLE_SERVER
				this.ServerController.TagChanged -= value;
#endif
			}
		}

		/// <summary>
		/// タグを取得します｡
		/// </summary>
		/// <param name="stationId">StationId</param>
		/// <param name="caster">Caster</param>
		/// <returns>取得した､タグのリスト｡</returns>
		/// <remarks>
		/// 戻り値に含まれる､タグはマイタグのみです｡
		/// UserTagはサーバからの取得完了後に､イベントで通知されます｡
		/// </remarks>
		public IList<TagModel> GetTags(string stationId, string caster)
		{
#if ENABLE_SERVER
			ServerController.Get(stationId, caster);
#endif
			return LocalController.GetTag(stationId, caster);
		}

		/// <summary>
		/// タグを削除します｡
		/// </summary>
		/// <param name="stationId">StationId</param>
		/// <param name="caster">Caster</param>
		/// <param name="tags">削除するTagのリスト</param>
		public void DeleteTags(string stationId, string caster, IList<TagModel> tags)
		{
			var FilterdTags = Filter(tags);

			if (FilterdTags.Count != 0)
			{
#if ENABLE_SERVER
				ServerController.Delete(stationId, caster, FilterdTags);
#endif
				LocalController.Delete(stationId, caster, FilterdTags);
			}
		}


		/// <summary>
		/// Tagを追加します｡
		/// </summary>
		/// <param name="stationId">StationId</param>
		/// <param name="caster">Caster</param>
		/// <param name="tags">追加するTagのリスト</param>
		/// <remarks>
		/// 引数のいずれかが不正な場合は例外無しで処理を行わず終了します｡
		/// 
		/// <para>タグは以下の要件を見たいしている場合にのみ､処理されます｡</para>
		/// <para>1.空文字でないこと</para>
		/// <para>2.空白文字のみで構成されていないこと</para>
		/// <para>3.20文字以内であること(*1)</para>
		/// <para>(*1)UTF-16のサロゲートペアを利用する文字は2文字としてカウントされます｡</para>
		/// </remarks>
		public void AddTags(string stationId, string caster, IList<TagModel> tags)
		{
			var FilterdTags = Filter(tags);

			if (FilterdTags.Count != 0)
			{
#if ENABLE_SERVER
				ServerController.Add(stationId, caster, tags);
#endif
				LocalController.Add(stationId, caster, tags);
			}
		}

		/// <summary>
		/// Casterタグを取得します｡
		/// </summary>
		/// <param name="source">説明文</param>
		/// <returns>抽出されたタグのうち､先頭二つをリスト形式で返します｡</returns>
		/// <remarks>
		/// <para>タグは以下の条件を満たしている必用があります｡</para>
		/// <para>1.[から始まり､]で終了していること｡</para>
		/// <para>2.20文字以内であること(*1)</para>
		/// <para>3.構成要素が空文字でないこと</para>
		/// <para>4.構成要素が空白文字のみで構成されていないこと｡</para>
		/// <para></para>
		/// <para>(*1)UTF-16のサロゲートペアを利用する文字は2文字としてカウントされます｡</para>
		/// </remarks>
		public static IList<TagModel> GetTags(string source)
		{
			Lexer lex = new Lexer(source);
			Parser p = new Parser(lex);
			ParseElement elem = null;
			List<TagModel> Ret = new List<TagModel>();

			while ((elem = p.GetElement()).Type != ParseElementType.EOF)
			{
				if (elem.Type == ParseElementType.Tag)
				{
					if (elem.Value.Length <= TagMaxLength && elem.Value.Trim().Length != 0)
					{
                        Ret.Add(new TagModel(elem.Value, TagKind.Caster));
						if (Ret.Count > CasterTagMaxCount)
						{
							break;
						}
					}
				}
			}

			return Ret;
		}

		private IList<TagModel> Filter(IList<TagModel> tags)
		{

			if (tags == null || tags.Count == 0)
			{
				return new List<TagModel>();
			}
			else
			{
				return tags.Where(tmp => tmp.Value != null && tmp.Value.Length < TagMaxLength && tmp.Value.Trim().Length != 0).ToList();
			}
		}


		#region IDisposable メンバ

		public void Dispose()
		{
			this.LocalController.Dispose();
#if ENABLE_SERVER
			this.ServerController.Dispose();
#endif
		}

		#endregion
	}
}
